<!DOCTYPE html>



  


<html class="theme-next muse use-motion" lang="zh-Hans">
<head>
  <meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css" />







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css" />

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css" />


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="Hexo, NexT" />










<meta name="description" content="微信搜『中间件兴趣圈』，回复『Java』获取200本优质电子书">
<meta property="og:type" content="website">
<meta property="og:title" content="中间件兴趣圈">
<meta property="og:url" content="https://www.codingw.net/index.html">
<meta property="og:site_name" content="中间件兴趣圈">
<meta property="og:description" content="微信搜『中间件兴趣圈』，回复『Java』获取200本优质电子书">
<meta property="og:locale">
<meta property="article:author" content="中间件兴趣圈">
<meta property="article:tag" content="中间件">
<meta name="twitter:card" content="summary">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '',
    scheme: 'Muse',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://www.codingw.net/"/>





  <title>中间件兴趣圈</title>
  








<meta name="generator" content="Hexo 5.4.0"></head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left 
  page-home">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/"  class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">中间件兴趣圈</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">微信搜『中间件兴趣圈』，回复『Java』获取200本优质电子书</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br />
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-question-circle"></i> <br />
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-question-circle"></i> <br />
            
            归档
          </a>
        </li>
      

      
    </ul>
  

  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            
  <section id="posts" class="posts-expand">
    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://www.codingw.net/posts/9b62ec51.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="中间件兴趣圈">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/posts/9b62ec51.html" itemprop="url">性能调优篇：困扰我半年之久的RocketMQ timeout exception 终于破解了</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-04-26T23:01:01+08:00">
                2021-04-26
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/rocketmq/" itemprop="url" rel="index">
                    <span itemprop="name">rocketmq</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          
             <span id="/posts/9b62ec51.html" class="leancloud_visitors" data-flag-title="性能调优篇：困扰我半年之久的RocketMQ timeout exception 终于破解了">
               <span class="post-meta-divider">|</span>
               <span class="post-meta-item-icon">
                 <i class="fa fa-eye"></i>
               </span>
               
                 <span class="post-meta-item-text">阅读次数&#58;</span>
               
                 <span class="leancloud-visitors-count"></span>
             </span>
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <div id="vip-container"><p>在内网环境中，超时问题，网络表示这个锅我不背。</p>
<p>笔者对于超时的理解，随着在工作中不断实践，其理解也越来越深刻，RocketMQ在生产环境遇到的超时问题，已经困扰了我将近半年，现在终于取得了比较好的成果，和大家来做一个分享。</p>
<p>本次技术分享，由于涉及到网络等诸多笔者不太熟悉的领域，如果存在错误，请大家及时纠正，实现共同成长与进步。</p>
<h2 id="1、网络超时现象"><a href="#1、网络超时现象" class="headerlink" title="1、网络超时现象"></a>1、网络超时现象</h2><p>时不时总是接到项目组反馈说生产环境MQ发送超时，客户端相关的日志截图如下：<br><img src="https://img-blog.csdnimg.cn/20210425231622567.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>今天的故事将从张图开始。</p>
<h2 id="2、问题排查"><a href="#2、问题排查" class="headerlink" title="2、问题排查"></a>2、问题排查</h2><h4 id="2-1-初步分析"><a href="#2-1-初步分析" class="headerlink" title="2.1 初步分析"></a>2.1 初步分析</h4><p>上图中有两条非常关键日志：</p>
<ul>
<li><p>invokeSync：wait response timeout exception<br>网络调用超时</p>
</li>
<li><p>recive response,but not matched any request</p>
<p>这条日志非常之关键，表示尽管客户端在获取服务端返回结果时超时了，但客户端最终还是能收到服务端的响应结果，只是此时客户端已经等待足够时间后放弃处理了。</p>
</li>
</ul>
<p>关于第二条日志，我再详细阐述一下其运作机制，其实也是用一条链接发送多个请求的编程套路。一条长连接，向服务端先后发送2个请求，客户端在收到服务端响应结果时，怎么判断这个响应结果对应的是哪个请求呢？如下图所示：<br><img src="https://img-blog.csdnimg.cn/2021042523164683.png#pic_center" alt="在这里插入图片描述"><br>客户端多个线程，通过一条连接发送了req1,req2两个请求，但在服务端通常都是多线程处理，返回结果时可能会先收到req2的响应，那客户端如何识别服务端返回的数据是对应哪个请求的呢？</p>
<p>解决办法是客户端在发送请求之前，会为该请求生成一个本机器唯一的请求id(requestId)，并且会采用Future模式，将requestId与Future对象放入一个Map中，然后将reqestId放入请求体中，服务端在返回响应结果时将请求ID原封不动的放入到响应结果中，当客户端收到响应时，先界面出requestId，然后从缓存中找到对应的Future对象，唤醒业务线程，将返回结构通知给调用方，完成整个通信。</p>
<p>故从这里能看到，客户端在指定时间内没有收到服务端的请求，但最终还是能收到，矛头直接指向Broker端，是不是Broker有瓶颈，处理很慢导致的。</p>
<h4 id="2-2-Broker端处理瓶颈分析"><a href="#2-2-Broker端处理瓶颈分析" class="headerlink" title="2.2 Broker端处理瓶颈分析"></a>2.2 Broker端处理瓶颈分析</h4><p>在我的“经验”中，RocketMQ消息发送如果出现瓶颈，通常会返回各种各样的Broker Busy，而且可以通过跟踪Broker端写入PageCache的数据指标来判断Broker是否遇到了瓶颈。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">grep &quot;PAGECACHERT&quot; store.log</span><br></pre></td></tr></table></figure>

<p>得到的结果类似如下截图：<br><img src="https://img-blog.csdnimg.cn/20210425231704970.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<blockquote>
<p>温馨提示:上图是我本机中的截图，当时分析问题的时候，MQ集群中各个Broker中这些数据，写入PageCache的时间没有超过100ms的。</p>
</blockquote>
<p>正是由于良好的pagecache写入数据，根据如下粗糙的网络交互特性，我提出将矛盾点转移到网络方面：<br><img src="https://img-blog.csdnimg.cn/20210425231721525.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>并且我还和业务方确定，虽然消息发送返回超时，但消息是被持久化到MQ中的，消费端也能正常消费，网络组同事虽然从理论上来说局域网不会有什么问题，但鉴于上述现象，网络组还是开启了网络方面的排查。</p>
<blockquote>
<p>温馨提示：最后证明是被我带偏了。</p>
</blockquote>
<h4 id="2-3-网络分析"><a href="#2-3-网络分析" class="headerlink" title="2.3 网络分析"></a>2.3 网络分析</h4><p>通常网络分析有两种手段，netstat 与网络抓包。</p>
<h5 id="2-3-1-netstat查看Recv-Q与Send-Q"><a href="#2-3-1-netstat查看Recv-Q与Send-Q" class="headerlink" title="2.3.1 netstat查看Recv-Q与Send-Q"></a>2.3.1 netstat查看Recv-Q与Send-Q</h5><p>我们可以通过netstat重点观察两个指标Recv-Q、Send-Q。<br><img src="https://img-blog.csdnimg.cn/20210425231741391.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<ul>
<li><p>Recv-Q<br>tcp通道的接受缓存区</p>
</li>
<li><p>Send-Q</p>
<p>tcp通道的发送缓存区</p>
</li>
</ul>
<p>在TCP中，Recv-Q与Send-Q的作用如下图所示：<br><img src="https://img-blog.csdnimg.cn/20210425231753360.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<ul>
<li>客户端调用网络通道，例如NIO的Channel写入数据，数据首先是写入到TCP的发送缓存区，如果发送发送区已满，客户端无法继续向该通道发送请求，从NIO层面调用Channel底层的write方法，会返回0，表示发送缓冲区已满，需要注册写事件，待发送缓存区有空闲时再通知上层应用程序可以发消息。</li>
<li>数据进入到发送缓存区后，接下来数据会随网络到达目标端，首先进入的是目标端的接收缓存区，如果与NIO挂钩的化，通道的读事件会继续，应用从接收缓存区中成功读取到字节后，会发送ACK给发送方。</li>
<li>发送方在收到ACK后，会删除发送缓冲区中的数据，如果接收方一直不读取数据，那发送方也无法发送数据。</li>
</ul>
<p>网络同事分布在客户端、MQ服务器上通过每500ms采集一次netstat ，经过对采集结果进行汇总，出现如下图所示：<br><img src="https://img-blog.csdnimg.cn/2021042523180547.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>从客户端来看，客户端的Recv-Q中会出现大量积压，对应的是MQ的Send-Q中出现大量积压。</p>
<p>从上面的通讯模型来看，再次推断是否是因为客户端从网络中读取字节太慢导致的，因为客户端为虚拟机，从netstat 结果来看，**疑似是客户端的问题(备注，其实最后并不是客户端的问题，请别走神)**。</p>
<h5 id="2-3-2-网络转包"><a href="#2-3-2-网络转包" class="headerlink" title="2.3.2 网络转包"></a>2.3.2 网络转包</h5><p>网络组同事为了更加严谨，还发现了如下的包：<br><img src="https://img-blog.csdnimg.cn/20210425231816335.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>这里有一个问题非常值得怀疑，就是客户端与服务端的滑动窗口只有190个字节，一个MQ消息发送返回包大概有250个字节左右，这样会已响应包需要传输两次才能被接收，一开始以为这里是主要原因，但通过其他对比，发现不是滑动窗口大于250的客户端也存在超时，从而判断这个不是主要原因，后面网络同事利用各种工具得出结论，网络不存在问题，是应用方面的问题。</p>
<p>想想也是，毕竟是局域网，那接下来我们根据netstat的结果，将目光放到了客户端的读性能上。</p>
<h4 id="2-4-客户端网络读性能瓶颈分析"><a href="#2-4-客户端网络读性能瓶颈分析" class="headerlink" title="2.4 客户端网络读性能瓶颈分析"></a>2.4 客户端网络读性能瓶颈分析</h4><p>为此，我为了证明读写方面的性能，我修改了RocketMQ CLient相关的包，加入了Netty性能采集方面的代码，其代码截图如下：<br><img src="https://img-blog.csdnimg.cn/20210425231828924.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>我的主要思路是判断客户端对于一个通道，每一次读事件触发，一个通道会进行多少次读取操作，如果一次读事件触发，需要触发很多次的读取，说明一个通道中确实积压了很多数据，网络读存在瓶颈。</p>
<p>但令人失望的是客户端的网络读并不存在瓶颈，部分采集数据如下所示：<br><img src="https://img-blog.csdnimg.cn/20210425231841964.png#pic_center" alt="在这里插入图片描述"><br>通过awk命令对其进行分析，发现一次读事件触发，大部分通道读两次即可将读缓存区中的数据抽取成功，读方面并不存在瓶颈，对awk执行的统计分析如下图所示：<br><img src="https://img-blog.csdnimg.cn/20210425231937860.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>那矛头又将指向Broker，是不是写到缓存区中比较慢呢？</p>
<h4 id="2-5-Broker端网络层面瓶颈"><a href="#2-5-Broker端网络层面瓶颈" class="headerlink" title="2.5 Broker端网络层面瓶颈"></a>2.5 Broker端网络层面瓶颈</h4><p>经过上面的分析，Broker服务端写入pagecache很快，维度将响应结果写入网络这个环节并未监控，是不是写入响应结果并不及时，大量积压在Netty的写缓存区，从而导致并未及时写入到TCP的发送缓冲区，从而造成超时。</p>
<p>笔者本来想也对其代码进行改造，从Netty层面去监控服务端的写性能，但考虑到风险较大，暂时没有去修改代码，而是再次认真读取了RocketMQ封装Netty的代码，在此次读取源码之前，我一直以为RocketMQ的网络层基本不需要进行参数优化，因为公司的服务器都是64核心的，而Netty的IO线程默认都是CPU的核数，但在阅读源码发现，在RocketMQ中与IO相关的线程参数有如下两个：</p>
<ul>
<li>serverSelectorThreads<br>默认值为3。</li>
<li>serverWorkerThreads<br>默认值为8。</li>
</ul>
<p>serverSelectorThreads，在Netty中，就是WorkGroup，即所谓的IO线程池，每一个线程池会持有一个NIO中的Selector对象用来进行事件选择，所有的通道会轮流注册在这3个线程中，绑定在一个线程中的所有Channel，会串行进行读写操作，即所有通道从TCP读缓存区，将数据写到发送缓存区都在这个线程中执行。</p>
<p>我们的MQ服务器的配置，CPU的核属都在64C及以上，用3个线程来做这个事情，显然有点太“小家子气”，该参数可以调优。</p>
<p>serverWorkerThreads，在Netty的线程模型中，默认情况下事件的传播(编码、解码)都在IO线程中，即在上文中提到的Selector对象所在的线程，为了降低IO线程的压力，RocketMQ单独定义一个线程池，用于事件的传播，即IO线程只负责网络读、写，读取的数据进行解码、写入的数据进行编码等操作，单独放在一个独立的线程池，线程池线程数量由serverWorkerThreads指定。</p>
<p>看到这里，终于让我心潮澎湃，感觉离真相越来越近了。参考Netty将IO线程设置为CPU核数的两倍，我第一波参数优化设置为serverSelectorThreads=16，serverWorkerThreads=32，在生产环境进行一波验证。</p>
<p>经过1个多月的验证，在集群机器数量减少(双十一之后缩容)，只出现过极少数的消息发送超时，基本可以忽略不计。</p>
<blockquote>
<p>温馨提示：关于Netty线程模型的详解，可以参考<a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s/kcSI0yQH3HxZt5KFU-M8_w">图解Netty线程模型</a>，关注公众号，回复NPDF，即可免费获得《让天下没有难学的Netty》网络通道篇的PDF。<br><img src="https://img-blog.csdnimg.cn/20210425232234863.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
</blockquote>
<h2 id="3、总结"><a href="#3、总结" class="headerlink" title="3、总结"></a>3、总结</h2><p>本文详细介绍了笔者排查MQ发送超时的精力，最终定位到的是RocketMQ服务端关于网络通信线程模型参数设置不合理。</p>
<p>之所以耗费这么长时间，其实有值得反思的地方，我被我的“经验”误导了，其实以前我对超时的原因直接归根于网络，理由是RocketMQ的写入PageCache数据非常好，基本都没有超过100ms的写入请求，以为RocketMQ服务端没有性能瓶颈，并没有从整个网络通信层考虑。</p>
<hr>
<p>好了，本文就介绍到这里了，<strong>键三连(关注、点赞、留言)是对我最大的鼓励，</strong>，当然可以加<strong>笔者微信：dingwpmz</strong>，备注CSDN，共同交流探讨。</p>
<p>最后分享笔者一个硬核的RocketMQ电子书，您将获得千亿级消息流转的运维经验。<br><img src="https://img-blog.csdnimg.cn/2021032212002741.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<p>获取方式：扫描如下二维码，关注【中间件兴趣圈】，回复RMQPDF即可获取。</p>
<p><img src="https://img-blog.csdnimg.cn/20190314214003962.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
</div>

			<script src="https://my.openwrite.cn/js/readmore.js" type="text/javascript"></script>
			<script>
			var isMobile = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i);
			if (!isMobile) {
			    var btw = new BTWPlugin();
			    btw.init({
			        "id": "vip-container",
			        "blogId": "18019-1573088808868-542",
			        "name": "中间件兴趣圈",
			        "qrcode": "https://img-blog.csdnimg.cn/20190314214003962.jpg",
			        "keyword": "more"
			    });
			}
			</script>
		
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://www.codingw.net/posts/cde2ec39.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="中间件兴趣圈">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/posts/cde2ec39.html" itemprop="url">学弟学妹们请不要错过自己的“黄金奋斗三年”，一份宝贵的失败经验送给大家</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-04-25T09:01:01+08:00">
                2021-04-25
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E6%8F%90%E5%8D%87%E8%AE%A4%E7%9F%A5/" itemprop="url" rel="index">
                    <span itemprop="name">提升认知</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          
             <span id="/posts/cde2ec39.html" class="leancloud_visitors" data-flag-title="学弟学妹们请不要错过自己的“黄金奋斗三年”，一份宝贵的失败经验送给大家">
               <span class="post-meta-divider">|</span>
               <span class="post-meta-item-icon">
                 <i class="fa fa-eye"></i>
               </span>
               
                 <span class="post-meta-item-text">阅读次数&#58;</span>
               
                 <span class="leancloud-visitors-count"></span>
             </span>
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <div id="vip-container"><p>一个好的大学一定会为我们加分不少，不少好学校一毕业就进入到行业的头部企业，以计算机为例，好的大学生一毕业就进入到阿里、百度、腾讯、头条等一线互联网企业，他的起点也许就是人的奋斗终点。</p>
<p>但我们现在已经迈入职场生涯的非重点院校毕业生来说，也并不是没有机会，初入职场的前３年是我们的奋斗黄金期。</p>
<p>在论证我上面的观点之前，我想先和大家分享一下我<strong>失败的经验，俗话说失败乃成功之母，希望我的失败经验能成为各位朋友成功的基石</strong>。</p>
<p>我职业生涯的前面四年，是呆在一家国企控股公司，主要从事的业务是电子政务方面的业务，是一个<strong>传统行业</strong>，虽然我很努力，解决工作中的问题显得得心应手，在公司所在的部门也能得到领导的赏识、同事们的认可。</p>
<p>但受限于所在平台的局限性以及公司的规模，薪资无法得到较大幅度的提升，萌生了离开的想法。</p>
<p>但让我所料不及的是我满怀信心去找工作的时候让我备受打击，阿里系企业直接将我秒杀，诸如HashMap的内存结构是什么？HashMap为什么不是线程安全的，什么大数据、高并发这些场景更是连想都没有想过，后面就选择一家外部公司，开始了探究互联网相关的技能。</p>
<p>回想这段经验，我有如下感悟与建议：</p>
<ol>
<li>第一份工作真的很重要，如果有好的学历背景，找工作时不能将就。</li>
<li>如果没有好的学历背景，也无需气馁，要一开始就为自己<strong>树立一个远大的目标：一定要凭着自己的努力，励志要进入一线互联网企业</strong>。</li>
<li>有了目标，在工作的第一年主要还是要扩大知识面，点到为止，以便功能好的完成工作，进入工作第二年后，就要开始打牢基础，探究原理，快速成长。</li>
</ol>
<p>经过我的技术分享，让我结识了一大批忠实的粉丝，从粉丝们的反馈来说，工作经验在2,3年左右进入BATM等头部互联网企业的人数很多，他们真切的感觉到深入学习一两门中间件是非常有必要的。</p>
<p>与进入BAT大厂的粉丝朋友们交流，<strong>得出一个经验：BAT大厂对求职者还不算非常严苛，不是太在乎你是否拥有高并发的项目实战经验，只需要具备响应的理论基础，并且有深入研究过工作中使用的中间件</strong>。</p>
<p>随着工作年限的增高，BAT开始对你的要求提高，如果你不具备高并发的项目经验，问题的思考高度将比较难满足其期望，进入大厂的几率会逐步下降，故请<strong>大家把握职场的前三年，努力奋斗，那如何奋斗呢？有没有一些技术路线图</strong>。</p>
<p>经过10年经验的体会，提炼出一套进入BAT大厂的路线图。</p>
<h4 id="１、Java基础"><a href="#１、Java基础" class="headerlink" title="１、Java基础"></a>１、Java基础</h4><p>java基础主要包含java集合、JUC并发包、Netty(NIO)网络编程。<br><img src="https://img-blog.csdnimg.cn/20210424181210669.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>Netty目前我已将我分享的内容整理成电子书：<br><img src="https://img-blog.csdnimg.cn/20210424181359264.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<h4 id="2、Java进阶"><a href="#2、Java进阶" class="headerlink" title="2、Java进阶"></a>2、Java进阶</h4><p>打牢Java基础，就需要对项目中使用的中间件深入研究，一来更好的为工作服务，另外可以让人觉得你对技术充满热情。故请挑选项目中常用的主流中间件，进行重点突破，<strong>笔者经过５年的分享，已对Java主流的12个中间件专栏，覆盖微服务、消息中间件、定时调度、数据异构等领域</strong>，<br><img src="https://img-blog.csdnimg.cn/20210424181856832.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>后续上述文章都会整成电子书，MQ专栏已整理两本电子书：<br><img src="https://img-blog.csdnimg.cn/20210424182015607.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>12个Java主流中间件专栏的获取链接：</p>
<ol>
<li><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s/wIUu5-BH394HSYySdX8WQA">12个Java主流中间件专栏</a></li>
<li>电子书获取方式：微信搜索 [中间件兴趣圈]，回复RMQPDF、NPDF可以获取笔者的三本电子书。</li>
</ol>
<p>如果本文对大家有所帮助，还希望大家能关注我，帮忙点个赞，有可以加我微信 dingwpmz，拉你入技术交流群，共同交流、探讨。</p>
<p>最后分享笔者一个硬核的RocketMQ电子书，您将获得千亿级消息流转的运维经验。<br><img src="https://img-blog.csdnimg.cn/2021032212002741.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<p>获取方式：扫描如下二维码，关注【中间件兴趣圈】，回复RMQPDF即可获取。</p>
<p><img src="https://img-blog.csdnimg.cn/20190314214003962.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
</div>

			<script src="https://my.openwrite.cn/js/readmore.js" type="text/javascript"></script>
			<script>
			var isMobile = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i);
			if (!isMobile) {
			    var btw = new BTWPlugin();
			    btw.init({
			        "id": "vip-container",
			        "blogId": "18019-1573088808868-542",
			        "name": "中间件兴趣圈",
			        "qrcode": "https://img-blog.csdnimg.cn/20190314214003962.jpg",
			        "keyword": "more"
			    });
			}
			</script>
		
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://www.codingw.net/posts/ea10028.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="中间件兴趣圈">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/posts/ea10028.html" itemprop="url">为什么Kafka在2.8版本中会“抛弃”Zookeeper，选择拥抱Raft?</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-04-20T23:47:01+08:00">
                2021-04-20
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E5%88%86%E5%B8%83%E5%BC%8F%E6%9E%B6%E6%9E%84/" itemprop="url" rel="index">
                    <span itemprop="name">分布式架构</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          
             <span id="/posts/ea10028.html" class="leancloud_visitors" data-flag-title="为什么Kafka在2.8版本中会“抛弃”Zookeeper，选择拥抱Raft?">
               <span class="post-meta-divider">|</span>
               <span class="post-meta-item-icon">
                 <i class="fa fa-eye"></i>
               </span>
               
                 <span class="post-meta-item-text">阅读次数&#58;</span>
               
                 <span class="leancloud-visitors-count"></span>
             </span>
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <div id="vip-container"><p>相信大家最近一定关注到一款重量级消息中间件Kafka发布了2.8版本，并且正式移除了对Zookeeper的依赖，背后的设计哲学是什么呢？仅仅只是减少了一个外部依赖吗？</p>
<p>答案显然不会这么简单，容我慢慢道来。</p>
<p>在解答为什么之前，我觉得非常有必要先来阐述一下Zookeeper的经典使用场景。</p>
<h2 id="1、Zookeeper的经典使用场景"><a href="#1、Zookeeper的经典使用场景" class="headerlink" title="1、Zookeeper的经典使用场景"></a>1、Zookeeper的经典使用场景</h2><p>zookeeper是伴随着大数据、分布式领域的兴起。大数据中的一个非常重要的议题是如何使用众多廉价的机器来实现可靠存储。</p>
<p>所谓廉价的机器就是发生故障的概率非常大，但单台的成本也非常低，分布式领域希望使用多台机器组成一个集群，将数据存储在多台机器上(副本)，为了方便实现数据一致性，通常需要从一个复制组中挑选一台主节点用户处理数据的读写，其他节点从主节点拷贝数据，当主节点宕机，需要自动进行重新选举，实现高可用。</p>
<p>上述场景中有一个非常重要的功能<strong>Leader选举</strong>，如何选举出一个主节点、并支持主节点宕机后自动触发重新选举，实现主从自动切换，实现<strong>高可用</strong>。</p>
<p>使用Zookeeper提供的临时顺序节点与事件监听机制，能非常轻松的实现Leader选举。<br><img src="https://img-blog.csdnimg.cn/20210420233107803.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>上面的t1,t2可以理解为一个组织中的多个成员，能提供相同的服务，但为了实现冷备效果(即同一时间只有一个成员对外提供服务，我们称之为Leader，当Leader宕机或停止服务后，该组织中的其他成名重新竞争Leader，然后继续对外提供服务)。</p>
<p>正如上图所示，<strong>Zookeeper是以集群部署的，能有效避免单点故障，并且集群内部提供了对数据的强一致性</strong>。</p>
<p>当成员需要竞争Leader时，借助Zookeeper的实现套路是向zookeeper中的一个数据节点(示例中为/app/order-service/leader)节点创建两个子节点，并且是<strong>顺序的临时节点</strong>。</p>
<p>客户端判断创建的节点的序号是否为/app/order-service/leader中序号最小的节点，<strong>如果是则成为Leader，对外提供服务</strong>；</p>
<p>如果序号不是最小的，<strong>则向自己前置的</strong>注册节点<strong>删除事件</strong>，一旦Leader代表的进程宕机，它与Zookeeper的会话失效后，与之关联的临时节点会被删除，一旦Leader创建的节点被删除，其后继节点会得到通知，从而再次触发选主，选举出新的Leader，继续对外提供服务，保质服务的高可用性。</p>
<p>回顾上述场景，借助Zookeeper能非常轻松的实现选主，为应用提高可用带来简便性，主要是利用了Zookeeper的几个特性：</p>
<ul>
<li>临时节点<br>临时节点是与会话关联的，一点创建该临时节点的会话结束，与之会被自动删除，无需应用方人工删除。</li>
<li>顺序节点</li>
<li>事件机制<br>借助与事件机制，Zookeeper能及时通知存活的其他应用节点，重新触发选举，使得实现自动主从切换变的非常简单。</li>
</ul>
<h2 id="2、Kafka对Zookeeper的迫切需求"><a href="#2、Kafka对Zookeeper的迫切需求" class="headerlink" title="2、Kafka对Zookeeper的迫切需求"></a>2、Kafka对Zookeeper的迫切需求</h2><p>Kafka中存在众多的Leader选举，熟悉Kafka的朋友应该知道，一个主题可以拥有多个分区(数据分片)，每一个数据分片可以配置多个副本，如何保证一个分区的数据在多个副本之间的一致性成为一个迫切的需求。</p>
<p>Kafka的实现套路就是一个分区的多个副本，从中选举出一个Leader用来承担客户端的读写请求，从节点从主节点处拷贝内容，Leader节点根据数据在副本中成功写入情况，进行抉择来确定是否写入成功。</p>
<p>Kafka中topic的分区分布示意图：<br><img src="https://img-blog.csdnimg.cn/20210420233325456.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br><strong>故此处需要进行Leader选举</strong>,而基于Zookeeper能轻松实现，从此一拍即合，开启了一段“蜜月之旅”。</p>
<h2 id="3、Zookeeper的致命弱点"><a href="#3、Zookeeper的致命弱点" class="headerlink" title="3、Zookeeper的致命弱点"></a>3、Zookeeper的致命弱点</h2><p>Zookeeper是集群部署，只要集群中超过半数节点存活，即可提供服务，例如一个由3个节点的Zookeeper，允许1个Zookeeper节点宕机，集群仍然能提供服务；一个由５个节点的Zookeeper，允许2个节点宕机。</p>
<p>但Zookeeper的设计是CP模型，即要保证数据的强一致性，必然在可用性方面做出牺牲。</p>
<p>Zookeeper集群中也存在所谓的Leader节点和从节点，Leader节点负责写，Leader与从节点可用接受读请求，但在Zookeeper内部节点在选举时整个Zookeeper无法对外提供服务。当然正常情况下选举会非常快，但在异常情况下就不好说了，例如Zookeeper节点发生full Gc，此时造成的影响将是毁灭性的。</p>
<p>Zookeeper节点如果频繁发生Full Gc，此时与客户端的会话将超时，由于此时无法响应客户端的心跳请求(Stop World)，从而与会话相关联的临时节点将被删除，注意，此时是所有的临时节点会被删除，Zookeeper依赖的事件通知机制将失效，整个集群的选举服务将失效。</p>
<p>站在高可用性的角度，Kafka集群的可用性不仅取决于自身，还受到了外部组件的制约，从长久来看，<strong>显然都不是一个优雅的方案</strong>。</p>
<p><strong>随着分布式领域相关技术的不断完善，去中心化的思想逐步兴起，去Zookeeper的呼声也越来越高，在这个进程中涌现了一个非常优秀的算法：Raft协议。</strong></p>
<p>Raft协议的两个重要组成部分：Leader选举、日志复制，而日志复制为多个副本提供数据强一致性提供了强一致性，<strong>并且一个显著的特点是Raft节点是去中心化的架构，不依赖外部的组件，而是作为一个协议簇嵌入到应用中的，即与应用本身是融合为一体的</strong>。</p>
<p>再以Kafka Topic的分布图举例，引用Raft协议的示例图如下：<br><img src="https://img-blog.csdnimg.cn/2021042023374699.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>关于Raft协议，本文并不打算深入进行探讨，但为选主提供了另外一种可行方案，而且还无需依赖第三方组件，何乐而不为呢？故最终Kafka在2.8版本中正式废弃了Zookeeper，拥抱Raft。</p>
<p>如果大家对Raft协议感兴趣，推荐阅读笔者关于Raft协议的系列文章：</p>
<ol>
<li><p><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s?__biz=MzIzNzgyMjYxOQ==&mid=2247484393&idx=1&sn=117208f86616ce7793d84fbe80c5ec5b&scene=19#wechat_redirect">初探raft协议</a></p>
</li>
<li><p><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s?__biz=MzIzNzgyMjYxOQ==&mid=2247484399&idx=1&sn=7fc8c42c8f3062ba83618c9d6577e95a&scene=19#wechat_redirect">Raft协议之Leader协议选主实现原理</a></p>
</li>
</ol>
<hr>
<hr>
<p>好了，本文就介绍到这里了，<strong>键三连(关注、点赞、留言)是对我最大的鼓励，</strong>，当然可以加<strong>笔者微信：dingwpmz</strong>，备注CSDN，共同交流探讨。</p>
<p>最后分享笔者一个硬核的RocketMQ电子书，您将获得千亿级消息流转的运维经验。<br><img src="https://img-blog.csdnimg.cn/2021032212002741.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<p>获取方式：扫描如下二维码，关注【中间件兴趣圈】，回复RMQPDF即可获取。</p>
<p><img src="https://img-blog.csdnimg.cn/20190314214003962.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
</div>

			<script src="https://my.openwrite.cn/js/readmore.js" type="text/javascript"></script>
			<script>
			var isMobile = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i);
			if (!isMobile) {
			    var btw = new BTWPlugin();
			    btw.init({
			        "id": "vip-container",
			        "blogId": "18019-1573088808868-542",
			        "name": "中间件兴趣圈",
			        "qrcode": "https://img-blog.csdnimg.cn/20190314214003962.jpg",
			        "keyword": "more"
			    });
			}
			</script>
		
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://www.codingw.net/posts/cb8c9e3c.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="中间件兴趣圈">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/posts/cb8c9e3c.html" itemprop="url">线上环境大规模RocketMQ集群不停机优雅升级实践</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-04-18T22:17:01+08:00">
                2021-04-18
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/rocketmq/" itemprop="url" rel="index">
                    <span itemprop="name">rocketmq</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          
             <span id="/posts/cb8c9e3c.html" class="leancloud_visitors" data-flag-title="线上环境大规模RocketMQ集群不停机优雅升级实践">
               <span class="post-meta-divider">|</span>
               <span class="post-meta-item-icon">
                 <i class="fa fa-eye"></i>
               </span>
               
                 <span class="post-meta-item-text">阅读次数&#58;</span>
               
                 <span class="leancloud-visitors-count"></span>
             </span>
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <div id="vip-container"><blockquote>
<p>见字如面,我是威哥,一个从普通二本院校毕业,从未曾接触分布式、微服务、高并发到通过技术分享实现职场蜕变，成长为RocketMQ社区优秀布道师、大厂资深架构师，出版《RocketMQ技术内幕》一书，在CSDN中记录了我的成长历程，欢迎大家关注我，随时可私信我，一起交流进步。</p>
</blockquote>
<p>接安全部门的行政要求，生产环境上百台RocketMQ机器必须在半个月内升级，必须支持ACL，规避安全风险。</p>
<p>RocketMQ集群的升级方案、落地实施就自然而然的落到了我的头上，<strong>本文不仅要介绍一下笔者是如何升级的，更想展示作为一名架构师，处理这些问题的方法论，展示大厂架构师的工作日常</strong>。</p>
<blockquote>
<p>温馨提示：关于ACL相关的内容，后续文章会单独分享从4.1.0版本升级到4.８并开启ACL的曲折经历。</p>
</blockquote>
<h2 id="1、版本升级的迫切性"><a href="#1、版本升级的迫切性" class="headerlink" title="1、版本升级的迫切性"></a>1、版本升级的迫切性</h2><p>说来惭愧，作为RocketMQ社区优秀布道师，笔者所在公司的RocketMQ服务端版本竟然还是4.1.0，RocketMQ在4.4.0版本之前是不支持ACL(访问控制)，对应生产环境中任意一台机器都可以订阅任意topic，在任意一台生产应用服务器都可以安装一个rocketmq-console，从而控制整个集群，拥有删除主题、删除消费组的权限，想想是不是<strong>后背发凉</strong>.</p>
<h2 id="2、升级方案"><a href="#2、升级方案" class="headerlink" title="2、升级方案"></a>2、升级方案</h2><h4 id="2-1-确定升级到的版本"><a href="#2-1-确定升级到的版本" class="headerlink" title="2.1 确定升级到的版本"></a>2.1 确定升级到的版本</h4><p>翻开RocketMQ升级日志，RocketMQ在4.4.0版本正式引入了ACL机制，故版本至少要升级到4.4.0，在业界使用开源版本有一个<strong>不成文的规则</strong>：通常不要使用最新的版本，不要充当小白鼠。</p>
<p><strong>但RocketMQ可以算是一个特殊</strong>。</p>
<p>通过仔细浏览RocketMQ的版本变更记录，我们不难发现RocketMQ Client 相关的变更非常少，即与用户关系紧密的消息发送、消息消费这块的代码非常的稳定，理论上基本不存在兼容性问题。并且每一个版本都修复了一些重大的BUG，性能提升也比较明显，故笔者这次决定“冒天下之大不韪”，决定将帮升级到<strong>最新版本4.8.0</strong>。</p>
<p>在这里在啰嗦一些，简单介绍一下RocketMQ几个具有里程杯意义的版本。</p>
<ul>
<li>RocketMQ4.3.0正式引入了事务消息，如果大家希望使用事务消息，其版本最低建议为 4.6.1。</li>
<li>RocketMQ4.4.0引入了ACL、消息轨迹，如果需要使用这些功能，其版本最低建议为 4.7.0。</li>
<li>RocketMQ4.5.0引入了多副本（主从切换），其版本建议使用4.7.0。</li>
<li>RocketMQ4.6.0引入了请求-响应模型。</li>
</ul>
<h4 id="2-2-升级思路"><a href="#2-2-升级思路" class="headerlink" title="2.2 升级思路"></a>2.2 升级思路</h4><p>版本升级的<strong>基本要求</strong>：业务不能停机，即要做到对业务无感知的升级。</p>
<p>如果机器足够的备用机器，<strong>最佳的版本迁移方案</strong>应该是先扩容再缩容，其示例图如下：<br><img src="https://img-blog.csdnimg.cn/20210417232603712.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>其主要的思路是先对Broker进行扩容，加入两台高版本的Broker服务器，加入到集群中，然后关闭低版本Broker的写权限，待消息过期后，将低版本移除，最后升级NameServer，完成不停机的在线迁移。</p>
<p>由于此次升级需要在半个月左右的时间内将RocketMQ集群所有的节点全部升级，无法提供这么多冷备节点，故先扩容、再缩容无法满足本次需求，本次只能基于已有的机器进行升级。</p>
<p>能否直接升级Broker端代码，但高版本的Broker直接使用低版本的Broker存储目录，即直接升级软件，其示例图如下：<br><img src="https://img-blog.csdnimg.cn/20210417232618440.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br><strong>核心思想是先停止老版本的Broker，然后使用新版本启动Broker，但使用旧的配置文件</strong>。</p>
<p>有了思路，接下来就是要验证方案的可行性。</p>
<h4 id="2-3-方案验证"><a href="#2-3-方案验证" class="headerlink" title="2.3 方案验证"></a>2.3 方案验证</h4><p><strong>理论归理论，在生产环境做任何变更之前，必须有充分的测试验证</strong>，版本升级重点需要验证<strong>兼容性</strong>问题。</p>
<h5 id="2-2-1-服务端版本兼容性验证"><a href="#2-2-1-服务端版本兼容性验证" class="headerlink" title="2.2.1 服务端版本兼容性验证"></a>2.2.1 服务端版本兼容性验证</h5><p><img src="https://img-blog.csdnimg.cn/20210417232633101.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>搭建一个上述MQ集群，其核心要点：</p>
<ul>
<li>高版本的Broker是否能向低版本的NameServer注册路由</li>
<li>低版本的Broker是否能向高版本的NameServer注册路由</li>
</ul>
<p>通过rocketmq-console，去创建多个个topic，看看其路由信息是否正确，<strong>经验证，符合预期</strong>。</p>
<h5 id="2-2-2-客户端与服务端兼容性验证"><a href="#2-2-2-客户端与服务端兼容性验证" class="headerlink" title="2.2.2 客户端与服务端兼容性验证"></a>2.2.2 客户端与服务端兼容性验证</h5><p>RocketMQ的客户端API其实比较单一，无非就是消息发送、批量发送，消息消费，由于4.1版本不支持事务消息，这次升级甚至都无需验证事务消息，验证的要点：</p>
<ul>
<li> 低版本的客户端是否能正常向高版本Broker发送消息，消费消息</li>
<li>高版本的客户端是否能向低版本的Broker发送消息，消费消息</li>
</ul>
<p>测试案例来自哪，其实都不需要我们自己写，直接用官方的Demo即可，其代码截图如下：<br><img src="https://img-blog.csdnimg.cn/20210417232647368.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>客户端验证在真正实施过程中，其实比服务端之间的验证要复杂的多，由于各个项目组使用的客户端版本不一，甚至有些项目组会使用c++、Python等其他非Java客户端，<strong>如何精确找到该集群中所有客户端的连接信息(客户端版本、语言类型)至关重要</strong>。</p>
<p>官方提供的版本，对消费组的连接信息还是支持的比较友好，我们可以通过写脚本，先查询系统中所有的消费组，然后遍历每一个消费组，可以查询这些消费组的IP地址、客户端版本、使用的语言等信息，但开源版本对生产者支持的不友好，没有一个可获取所有发送者相关的接口。</p>
<p>获取消费组消费端的连接方式如下图所示：<br><img src="https://img-blog.csdnimg.cn/20210417232659749.png#pic_center" alt="在这里插入图片描述"><br>故我们采取的方式，主要是基于消费组失败客户端类型，本次升级过程中，我也对RocketMQ做了一些定制化开发，<strong>可方便获取所有发送方的链接信息，后续会已提交PR的方式贡献给官方</strong>。</p>
<h5 id="2-2-3-Broker端存储格式验证"><a href="#2-2-3-Broker端存储格式验证" class="headerlink" title="2.2.3 Broker端存储格式验证"></a>2.2.3 Broker端存储格式验证</h5><p>由于没有空闲资源，本次要使用的升级方式是直接升级软件，但新老版本共用存储目录，基于RocketMQ的消息存储协议，从4.0.0版本之后就一直没有变化，其验证的关键点如下：</p>
<ul>
<li>4.8.0版本是否可以直接使用4.1.0生成的存储文件(commitlog等文件)</li>
<li>4.1.0版本是否可以直接使用4.8.0生成的存储文件</li>
</ul>
<p>为什么需要验证4.1.0版本能兼容4.8.0呢？因为如果升级失败，需要回滚，如果4.1.0版本不能兼容4.8.0的话，会让你没有退路，这在架构设计中是绝对不允许的。</p>
<p>经过验证发现，存储文件是相互兼容的。</p>
<h5 id="2-2-4-测试环境验证"><a href="#2-2-4-测试环境验证" class="headerlink" title="2.2.4 测试环境验证"></a>2.2.4 测试环境验证</h5><p>经过上面三步的验证，已经可以进行升级了，但升级之前，还要在测试环境稳定运行一天，可以将测试环境升级成如下架构：<br><img src="https://img-blog.csdnimg.cn/20210417232717798.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>即不同版本的混搭模式，接受测试环境所有应用服务器的验证，如果测试环境运行没有问题，即可在生产环境进行升级。</p>
<h4 id="2-4-实施方案"><a href="#2-4-实施方案" class="headerlink" title="2.4 实施方案"></a>2.4 实施方案</h4><p>有了上面升级方案，并且已经做了充分的验证，是可以在生产环境执行了，在执行之前，需要对理论设计输出可执行可落地的实施方案，<strong>实施方案必须要包括回滚操作，并且这个回滚操作一定要比较容易执行，否则你的方案一定是不那么可靠的</strong>。</p>
<p>接下来重点阐述一下实施过程中一些关键步骤，整个升级步骤才有滚动升级，即逐台升级。</p>
<p>1、关闭一个Broker的写权限</p>
<p>关闭Broker写权限，让应用将流量平滑迁移到其他节点，这样可以有效避免在对该机器进行重启时对业务造成的影响。</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sh ./mqadmin updateBrokerConfig -b 192.168.x.x:10911 -n 192.168.xx.xx:9876 -k brokerPermission -v 4</span><br></pre></td></tr></table></figure>

<p>2、带Broker写入、消费tps接近０时，关闭broker</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">ps -ef | grep java</span><br><span class="line">kill pid</span><br></pre></td></tr></table></figure>

<p>3、使用新版本启动Broker</p>
<p>注意，此过程使用的配置文件为老版本的配置，故此时并没有开启写权限，启动并不会对客户端消息写入造成影响。</p>
<p>４、开启写权限</p>
<p>待新版本启动成功后，既可以开启写权限</p>
<figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sh ./mqadmin updateBrokerConfig -b 192.168.xx.xx:10911 -n 192.168.xx.xx:9876 -k brokerPermission -v 6</span><br></pre></td></tr></table></figure>

<p>观察流量。</p>
<p>重复上述步骤即可完成Broker的升级。</p>
<p>关于Nameserver的升级就更加容易了，采用滚动升级，kill掉老版本的nameserver，在原机器上启动新版本的nameserver即可。</p>
<h2 id="3、花絮"><a href="#3、花絮" class="headerlink" title="3、花絮"></a>3、花絮</h2><p> 最后和大家再分享一个小小的插曲。尽管上面的方案非常详细，并且经过反复的测试，但MQ在我们公司的重要性实在太重要，运维小伙伴在操作时不敢下手，在操作时他要我在身边看着，这个时候，作为架构师的我们要敢于承担责任，明确告知，只要你操作正确，出力故障由我来承担，这也是我个人觉得作为架构师一个非常重要的软技能：<strong>对你负责的技术具有掌控力，并且敢于担当。</strong></p>
<hr>
<p>好了，本文就介绍到这里了，<strong>您的一键三连是对我最大的鼓励</strong>，当然可以加<strong>笔者微信：dingwpmz</strong>，备注CSDN，共同交流探讨。</p>
<p>最后分享笔者一个硬核的RocketMQ电子书，您将获得千亿级消息流转的运维经验。<br><img src="https://img-blog.csdnimg.cn/2021032212002741.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<p>获取方式：扫描如下二维码，关注【中间件兴趣圈】，回复RMQPDF即可获取。</p>
<p><img src="https://img-blog.csdnimg.cn/20190314214003962.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
</div>

			<script src="https://my.openwrite.cn/js/readmore.js" type="text/javascript"></script>
			<script>
			var isMobile = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i);
			if (!isMobile) {
			    var btw = new BTWPlugin();
			    btw.init({
			        "id": "vip-container",
			        "blogId": "18019-1573088808868-542",
			        "name": "中间件兴趣圈",
			        "qrcode": "https://img-blog.csdnimg.cn/20190314214003962.jpg",
			        "keyword": "more"
			    });
			}
			</script>
		
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://www.codingw.net/posts/4a375441.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="中间件兴趣圈">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/posts/4a375441.html" itemprop="url">如何吃透一个java项目</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-04-15T15:17:01+08:00">
                2021-04-15
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E6%8F%90%E5%8D%87%E8%AE%A4%E7%9F%A5/" itemprop="url" rel="index">
                    <span itemprop="name">提升认知</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          
             <span id="/posts/4a375441.html" class="leancloud_visitors" data-flag-title="如何吃透一个java项目">
               <span class="post-meta-divider">|</span>
               <span class="post-meta-item-icon">
                 <i class="fa fa-eye"></i>
               </span>
               
                 <span class="post-meta-item-text">阅读次数&#58;</span>
               
                 <span class="leancloud-visitors-count"></span>
             </span>
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <div id="vip-container"><p>见字如面,我是威哥,一个从普通二本院校毕业,从未曾接触分布式、微服务、高并发到通过<strong>技术分享</strong>实现职场蜕变，成长为RocketMQ社区优秀布道师、大厂资深架构师，出版《RocketMQ技术内幕》一书，在CSDN中记录了我的成长历程，欢迎大家关注我，随时可私信我，一起交流进步。</p>
<p>我在尝试学习RocketMQ之前未曾接触过消息中间件，通过自己提炼的学习方法，最终出版《RocketMQ技术内幕》一书，并且成为RocketMQ社区优秀布道师，让我有了一个非常亮眼的标签，极大了提高的我的职场竞争力，在这里和大家分享一下我的方法。</p>
<p>我根据多年的阅读经验，整理了这么一套方法：</p>
<ul>
<li>了解这款软件的使用场景、以及架构设计中将承担的责任。</li>
<li>寻找官方文档，从整体上把握这款软件的设计理念。</li>
<li>搭建自己的开发调试环境，运行官方提供Demo示例，为后续深入研究打下基础。</li>
<li>先主干流程再分支流程，注意切割，逐个击破。<br>接下来分享一下我在阅读 RocketMQ 源码时的一些经历，尽量让上述理论具有画面感。</li>
</ul>
<h2 id="1、了解-RocketMQ的应用场景"><a href="#1、了解-RocketMQ的应用场景" class="headerlink" title="1、了解 RocketMQ的应用场景"></a>1、了解 RocketMQ的应用场景</h2><p>MQ的使用场景是比较清晰的，它的两大基本职责是解耦与削峰填谷。<br>举一个最简单的场景：新用户注册送积分、送优惠券场景，其原始架构设计通常如下：<br><img src="https://img-blog.csdnimg.cn/20210414230125757.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>可以看出用户注册和发优惠券，送积分是紧耦合的， 随着业务不断发展，活动部门提出在春节期间用户注册不送积分，发优惠券，而是赠送一个新春礼包，如果基于上述架构的话，需要去改动用户注册的主流程，<strong>违背了设计模式中的对修改关闭、对扩展开放的设计理念</strong>。</p>
<p>MQ的出现，可以很好地解决上面的问题：<br><img src="https://img-blog.csdnimg.cn/20210414230231368.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<p>通过引入MQ，用户注册主流程只需要完成注册逻辑，并向MQ发送一条消息，然后活动模块（送积分、送优惠券、送礼包）只需要订阅MQ中的消息，进行对应的处理。</p>
<p>这样消息注册主流程会非常的简单，不管活动种类如何变化，注册流程无需更改，这样就实现了解耦。</p>
<h2 id="2-通读官方文档，从全局把握其设计理念"><a href="#2-通读官方文档，从全局把握其设计理念" class="headerlink" title="2  通读官方文档，从全局把握其设计理念"></a>2  通读官方文档，从全局把握其设计理念</h2><p>了解使用场景以后，接下我们可以去查阅官方文档，主要包括用户设计文档（架构设计），用户使用手册等，从全局了解其设计理念。<br><img src="https://img-blog.csdnimg.cn/20210414230253471.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>通过通读官方文档，不仅可以得出MQ的整体脉络（例如NameServer路由发现、消息发送、消息存储、消息消费、消息过滤），也能对顺序消费，零拷贝、同步刷盘、异步刷盘等“高端大气上档次”的高级特性产生兴趣与好奇，驱动我们去阅读其源码，探究其实现细节，使得我们在阅读源码中进行一定的自我思考成为了可能。</p>
<h2 id="3、搭建开发调试环境"><a href="#3、搭建开发调试环境" class="headerlink" title="3、搭建开发调试环境"></a>3、搭建开发调试环境</h2><p>不同的系统搭建方式也不同，我这里有一篇文章，<a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/s?__biz=MzIzNzgyMjYxOQ==&mid=2247485300&idx=1&sn=065c3106340c91117bce1ec8dce9e6eb&chksm=e8c3f080dfb479966c1292c1a82ce4009d9f1b9d3a60464c8caec300590c359b8b4743961068&scene=21#wechat_redirect">手把手教你搭建RocketMQ调试环境</a>，这里就不细说了。</p>
<h2 id="4-先主干，再分支"><a href="#4-先主干，再分支" class="headerlink" title="4 先主干，再分支"></a>4 先主干，再分支</h2><p>在搭建好本地开发环境后，切忌直接用Debug去跟踪消息发送的整体流程，因为这个流程实在是太长了，从比较粗粒度来看其流程如下图所示：<br><img src="https://img-blog.csdnimg.cn/20210414230455489.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>如果大家想一次性将上述流程的源码全部看一遍，几乎是不可能的。 因为消息发送高可用设计、消息存储、刷盘、同步等机制，每个点详细展开的工作都是海量的，我们没有这么多连续的时间，所以适当的拆分非常有必要。</p>
<p>经过这样一分解，就能专注理解其某一块的设计原理，所需要的连续时间也能大大减少，一口一口“吃”，最终完成整个体系的理解。</p>
<hr>
<p>好了，本文就介绍到这里了，<strong>您的一键三连是对我最大的鼓励</strong>，当然可以加<strong>笔者微信：dingwpmz</strong>，备注CSDN，共同交流探讨。</p>
<p>最后分享笔者一个硬核的RocketMQ电子书，您将获得千亿级消息流转的运维经验。<br><img src="https://img-blog.csdnimg.cn/2021032212002741.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<p>获取方式：扫描如下二维码，关注【中间件兴趣圈】，回复RMQPDF即可获取。</p>
<p><img src="https://img-blog.csdnimg.cn/20190314214003962.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
</div>

			<script src="https://my.openwrite.cn/js/readmore.js" type="text/javascript"></script>
			<script>
			var isMobile = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i);
			if (!isMobile) {
			    var btw = new BTWPlugin();
			    btw.init({
			        "id": "vip-container",
			        "blogId": "18019-1573088808868-542",
			        "name": "中间件兴趣圈",
			        "qrcode": "https://img-blog.csdnimg.cn/20190314214003962.jpg",
			        "keyword": "more"
			    });
			}
			</script>
		
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://www.codingw.net/posts/c0bbe727.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="中间件兴趣圈">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/posts/c0bbe727.html" itemprop="url">阿里二面：NIO为什么不适合文件上传场景、如何优雅解决</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-04-14T22:17:01+08:00">
                2021-04-14
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/netty/" itemprop="url" rel="index">
                    <span itemprop="name">netty</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          
             <span id="/posts/c0bbe727.html" class="leancloud_visitors" data-flag-title="阿里二面：NIO为什么不适合文件上传场景、如何优雅解决">
               <span class="post-meta-divider">|</span>
               <span class="post-meta-item-icon">
                 <i class="fa fa-eye"></i>
               </span>
               
                 <span class="post-meta-item-text">阅读次数&#58;</span>
               
                 <span class="leancloud-visitors-count"></span>
             </span>
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <div id="vip-container"><p>见字如面,我是威哥,一个从普通二本院校毕业,从未曾接触分布式、微服务、高并发到通过<strong>技术分享</strong>实现职场蜕变，成长为RocketMQ社区优秀布道师、大厂资深架构师，出版《RocketMQ技术内幕》一书，在CSDN中记录了我的成长历程，欢迎大家关注我，随时可私信我，一起交流进步。</p>
<p>该系列已分别介绍了服务端、客户端的启动流程，本文将重点剖析Netty是如何封装NIO的读事件。</p>
<blockquote>
<p>温馨提示：本文虽然是源码分析，但强烈建议精读，因为根据源码阐述其背后的设计哲学，也用黑体进行了标注，请特别留意。</p>
</blockquote>
<p>在阅读本篇文章之前，请稍微思考如下几个问题：</p>
<ul>
<li>NIO为什么不适合文件上传等场景</li>
<li>NIO如何避免一个超大数据传送的连接对其他请求的影响</li>
<li>NIO如何处理半关闭</li>
</ul>
<h2 id="１、读事件概述"><a href="#１、读事件概述" class="headerlink" title="１、读事件概述"></a>１、读事件概述</h2><p>关于Read事件在SocketChannel与ServerSocketChannel所对应的操作不一样，在SocketChannel中，则对应数据读，而在ServerSocketChannel中则被被封装成接受客户端的连接请求。</p>
<p>NIO read事件入口在NioEventLoop的processSelectedKey方法，截图如下：<br><img src="https://img-blog.csdnimg.cn/20210313225717887.png#pic_center" alt="在这里插入图片描述"><br>其核心入口为UnSafe的read方法，关于UnSafe的类层次结构如下图所示：<br><img src="https://img-blog.csdnimg.cn/20210313230025369.png#pic_center" alt="在这里插入图片描述"></p>
<ul>
<li>AbstractNioByteChannel$NioByteUnsafe#read<br>SocketChannel对应的读事件处理流程，即IO读的处理实现。</li>
<li>AbstractNioMessageChannel$NioMessageUnsafe#read<br>ServerSocketChannle对应的读事件处理流程。</li>
</ul>
<p>接下来将分别介绍这两个流程。</p>
<h2 id="2、IO读事件从处理流程"><a href="#2、IO读事件从处理流程" class="headerlink" title="2、IO读事件从处理流程"></a>2、IO读事件从处理流程</h2><p>IO读事件由AbstractNioByteChannel内部类AbstractNioUnsafe的read方法实现，接下来重点剖析该方法，从中窥探Netty对IO读事件的处理。<br><img src="https://img-blog.csdnimg.cn/20210313233530876.png#pic_center" alt="在这里插入图片描述"><br>Step1：如果没有开启自动注册读事件，在每一次读时间处理过后会取消读事件，默认为自动注册。</p>
<blockquote>
<p>温馨提示：如果通道不注册读事件，将无法从通道中读取数据，即无法处理请求或接受响应。</p>
</blockquote>
<p>如果没有开启自动读事件，需要应用程序在需要的时候手动调用通道的read方法。</p>
<p>取消读事件，Netty基于NIO给出了非常标准的实现，基本可以当场模板代码使用：<br><img src="https://img-blog.csdnimg.cn/20210313235646288.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>其实现关键点：首先判断键值对是否有效，然后通过位运算进行取消注册。<br><img src="https://img-blog.csdnimg.cn/20210314000046309.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>Step2：创建接受缓存区内存分配器，这里有两个关键点：</p>
<ul>
<li>maxMessagesPerRead<br>每一个通道在一次读事件处理过程中最多可以调用底层Socket进行读取的次数，默认为16次，<strong>这里的设计哲学是避免一个通道需要读取太多的数据，从而影响其他通道的数据读，因为在一个事件选择器中多个通道的读事件是串行执行的</strong>。</li>
<li>RecvByteBufAllocator<br>接受缓冲区的内存分配策略，分为分配固定大小(不够时扩容)、动态变化(根据历史分配的大小，动态条件合适的内存大小)，<strong>这里主要的设计哲学是合理利用内存，并减少扩容，提高内存的分配效率与使用效率</strong>。</li>
</ul>
<p><img src="https://img-blog.csdnimg.cn/20210314001303653.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>Step3：循环处理读事件，最多处理maxMessagePerRead。接下来探讨一下单次读事件的处理流程。<br><img src="https://img-blog.csdnimg.cn/20210314125459781.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>Step4：进行一次IO读处理，其处理有如下几个关键点：</p>
<ul>
<li>首先分配一个ByteBuf，俗称接收缓存区，用来存放从网络中读取的内容。</li>
<li>获取一下分配到的累积缓存区可写的字节数，<strong>这个后面有妙用</strong>。</li>
<li>调用底层网络读API从网卡中读取数据，NIO的读取实现如下所示：<br><img src="https://img-blog.csdnimg.cn/20210314125905909.png#pic_center" alt="在这里插入图片描述"><br>即调用NIO中的SocketChannel进行读数据，其返回参数表示这次从网卡中读取到的字节数。<strong>如果读取到的字节少于0，则表示对端通道已关闭，己端也需要进行相应的处理，例如关闭通道</strong>。</li>
<li>读到一批数据后，会通过事件传播机制向事件链中传播channelRead事件，触发后续对该批数据的处理。</li>
</ul>
<p><img src="https://img-blog.csdnimg.cn/20210314130347920.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>Step5：判断该通道是否需要继续读，其基本依据如下：</p>
<ul>
<li>如果未开启自动注册读事件，读完一次之后将不再继续读取。</li>
<li>如果本次读取到的字节数小于接收缓存区，说明此刻网卡中没有可读数据，等下一次读事件触发再继续读。</li>
</ul>
<p><img src="https://img-blog.csdnimg.cn/202103141344362.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>Step6：一次或多次读操作结束后，会触发一次读完成事件，向整个事件链传播。</p>
<p>整个网络读处理流程就介绍到这了。</p>
<h2 id="3、接受连接处理流程"><a href="#3、接受连接处理流程" class="headerlink" title="3、接受连接处理流程"></a>3、接受连接处理流程</h2><p>在Netty中，服务端接收客户端的连接请求(OP_ACCEPT)，被封装在channelRead 事件中，其代码入口为：AbstractNioMessageChannel　的内部类NioMessageUnsafe的read方法。<br><img src="https://img-blog.csdnimg.cn/20210314135118821.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>其大概的实现要点在前面已经介绍，这里主要看一下NioServerSocketChannel的doReadMessage。<br><img src="https://img-blog.csdnimg.cn/20210314135339382.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>通过使用底层的NIO接受一个连接，并获取NioSocketChannel对象。然后继续该对象向下传播channelRead事件，在后续的处理器中对该对象进行操作，例如将其注册读事件，从而触发网络的读操作，关于NioSocketChannel如何绑定读事件、注册业务相关的事件监听器机制已经在<a target="_blank" rel="noopener" href="https://blog.csdn.net/prestigeding/article/details/113873810?spm=1001.2014.3001.5501">Netty进阶：手把手教你如何编写一个NIO服务端</a>中详细介绍，本文就不再重复。</p>
<p>好了，本文就介绍到这里了，想必对开头部门提出的问题有了自己的答案了吧，<strong>您的一键三连是对我最大的鼓励</strong>，当然可以加<strong>笔者微信：dingwpmz</strong>，备注CSDN，共同交流探讨。</p>
<hr>
<p>为了方便大家学习Netty，笔者将RocketMQ的网络通信模块抽取出一个通用的Netty开发框架，大家可以从github上下载，堪称Netty界最强Hello World。<br><img src="https://img-blog.csdnimg.cn/20210314140644894.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>下载链接：<a target="_blank" rel="noopener" href="https://github.com/dingwpmz/netty-learning">Netty通用开发框架github地址</a></p>
<p>分享笔者一个硬核的RocketMQ电子书：<br><img src="https://img-blog.csdnimg.cn/2021032212002741.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<p>获取方式：扫描如下二维码，关注【中间件兴趣圈】，回复RMQPDF即可获取。</p>
<p><img src="https://img-blog.csdnimg.cn/20190314214003962.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
</div>

			<script src="https://my.openwrite.cn/js/readmore.js" type="text/javascript"></script>
			<script>
			var isMobile = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i);
			if (!isMobile) {
			    var btw = new BTWPlugin();
			    btw.init({
			        "id": "vip-container",
			        "blogId": "18019-1573088808868-542",
			        "name": "中间件兴趣圈",
			        "qrcode": "https://img-blog.csdnimg.cn/20190314214003962.jpg",
			        "keyword": "more"
			    });
			}
			</script>
		
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://www.codingw.net/posts/434e75fd.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="中间件兴趣圈">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/posts/434e75fd.html" itemprop="url">大厂Offer收割机：Netty优雅的网络写设计之连坏4问</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-04-01T00:17:01+08:00">
                2021-04-01
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/netty/" itemprop="url" rel="index">
                    <span itemprop="name">netty</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          
             <span id="/posts/434e75fd.html" class="leancloud_visitors" data-flag-title="大厂Offer收割机：Netty优雅的网络写设计之连坏4问">
               <span class="post-meta-divider">|</span>
               <span class="post-meta-item-icon">
                 <i class="fa fa-eye"></i>
               </span>
               
                 <span class="post-meta-item-text">阅读次数&#58;</span>
               
                 <span class="leancloud-visitors-count"></span>
             </span>
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <div id="vip-container"><p>该系列已分别介绍了服务端、客户端的启动流程、网络读事件处理流程，本文将重点剖析Netty是如何封装NIO的写事件。</p>
<blockquote>
<p>温馨提示：本文虽然是源码分析，但强烈建议精读，因为根据源码阐述其背后的设计哲学，也用黑体进行了标注，请特别留意。</p>
</blockquote>
<p>在阅读本篇文章之前，请稍微思考如下几个问题：</p>
<ul>
<li>写事件需要先注册才能往通道中写入数据？</li>
<li>什么时候需要向通道注册写事件呢？</li>
<li>业务线程池执行业务逻辑后，是如何通过IO线程将数据写入到网络中的呢？</li>
<li>Netty中如何针对写限流</li>
</ul>
<h2 id="1、写事件概述"><a href="#1、写事件概述" class="headerlink" title="1、写事件概述"></a>1、写事件概述</h2><p>写事件，顾名思义，就是将数据写入网络，通过网络传输给接收端，通常我们知道业务都会在专属的业务线程池中执行，那数据是如何通过IO线程写入网络中的呢？<br><img src="https://img-blog.csdnimg.cn/202103292342433.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>正如上图中的线程模型，业务线程池是如何将数据通过IO线程写入网络中的呢？</p>
<p>接下来我们将带着问题，尝试看Netty是如何封装NIO的写事件。</p>
<h2 id="2、写事件处理流程分析"><a href="#2、写事件处理流程分析" class="headerlink" title="2、写事件处理流程分析"></a>2、写事件处理流程分析</h2><p>在Netty中，写事件的处理入口为NioEventLoop的processSelectedKey方法：<br><img src="https://img-blog.csdnimg.cn/20210329235337678.png#pic_center" alt="在这里插入图片描述"><br>根据调用链，最终调用AbstractChannel的内部类AbstractUnsafe的flush0方法。<br><img src="https://img-blog.csdnimg.cn/20210330211203768.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="AbstractUnsafe#flush0"><br>该方法有三个实现要点：</p>
<ul>
<li>获取写缓存队列，如果写缓存队列为空，则跳过本次写事件。每一个通道独享一个写缓存队列，写事件触发执行的动作就是要将写缓存中的数据写入到网络中。</li>
<li>如果通道处于未激活状态，需要清理写缓存区</li>
<li>通过调用doWrite方法将写缓存中的数据写入网络通道中。</li>
</ul>
<p>接下来将详细介绍上述关键步骤。</p>
<h4 id="2-1-NIO写事件的优雅封装"><a href="#2-1-NIO写事件的优雅封装" class="headerlink" title="2.1 NIO写事件的优雅封装"></a>2.1 NIO写事件的优雅封装</h4><p>首先，我们将写缓存区当成一个黑盒，先重点看一下doWrite方法的实现，窥探一下Netty是如何基于NIO来处理网络写入的。<br><img src="https://img-blog.csdnimg.cn/2021033021290030.png#pic_center" alt="在这里插入图片描述"><br>Step1：如果写缓存区中没有可写的数据，<strong>取消注册写事件</strong>。我们来看一下取消写事件的经典实现技巧：<br><img src="https://img-blog.csdnimg.cn/202103302132261.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>首先判断一下注册键是否有效，然后通过为位运算，取消写事件。</p>
<p><strong>思考题：问题来了，取消写事件，从系统层面就无法继续触发写操作了，那后续如何触发写事件呢？</strong></p>
<p><img src="https://img-blog.csdnimg.cn/2021033021413799.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>写事件的处理要考虑如下几个问题：</p>
<ul>
<li>本次写缓存区中数据是否写完？</li>
<li>如果底层网络Socket缓存区积压，导致写缓冲区未写完如何处理？</li>
<li>如果网络缓存区数据特别大又如何处理？</li>
</ul>
<p>Netty给出的解决方案如下：</p>
<ul>
<li>通过底层NIO的SocketChannel的write方法将数据写入到Socket缓存区，<strong>如果返回值为０，表示Socket缓冲区已满，需要暂停写入，具体做法，注册写事件，等待下次继续写入。</strong></li>
<li>如果写缓存区的数据全部处理完毕，<strong>可取消注册写事件</strong>，避免毫无意义的写事件就绪。</li>
<li>如果写缓存区中数据很大，为了避免单个通道对其他通道的影响，默认设置单次写事件最多调用底层NIO SocketChannel的write方法次数，默认为16。</li>
</ul>
<p>写事件的核心处理要点就介绍到这里了。</p>
<h4 id="2-2-通道写缓冲区详解"><a href="#2-2-通道写缓冲区详解" class="headerlink" title="2.2 通道写缓冲区详解"></a>2.2 通道写缓冲区详解</h4><p>在Netty中调用通道的write方法并不会立即将数据写入到底层网络Socket中，而是写入到“写缓存区”，为应用级别的缓存区，即<strong>ChannelOutboundBuffer</strong>，这是Netty实现<strong>写操作最重要的一个数据结构</strong>。</p>
<h5 id="2-2-1-类图"><a href="#2-2-1-类图" class="headerlink" title="2.2.1 类图"></a>2.2.1 类图</h5><p>ChannelOutboundBuffer 的核心类图如下：<br><img src="https://img-blog.csdnimg.cn/20210331231749520.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>核心属性与方法简介：</p>
<ul>
<li>FastThreadLocal&lt;ByteBuffer[]&gt; NIO_BUFFERS<br>可以看出是线程本地变量ThreadLocal的优化版本，存储一个一个ByteBuffer数组。</li>
<li>Channel channel<br>该写缓存区所属的通道，每一个Channel独享一个写缓冲区。</li>
<li>Entry flushedEntry<br>表示第一个被刷新的Entry,在写入时，从该Entry开始写。</li>
<li>Entry unflushedEntry<br>在链表中第一个未刷新的节点(未刷新链表中第一个节点)。</li>
<li>Entry tailEntry<br>在链表中尾部的节点。</li>
<li>int flushed<br>待写入的entry个数，这个数据代表在执行一次真正的flush(flush0),将会有多少个entry中的内容会被写入到通道。</li>
</ul>
<p>接下来我们按照写事件对待写入缓存区方法调用的顺序来讲解一下该方法的核心实现逻辑。</p>
<h5 id="2-2-2-size方法详解"><a href="#2-2-2-size方法详解" class="headerlink" title="2.2.2  size方法详解"></a>2.2.2  size方法详解</h5><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">size</span><span class="params">()</span> </span>&#123;</span><br><span class="line">    <span class="keyword">return</span> flushed;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>返回本次写缓存区可期望刷新的消息个数(Entry)。在NioSocketChannel的doWriter方法中，如果isEmpty返回true,直接结束本次写入操作，更加准确的是结束本次flush操作。</p>
<p>flushed该字段代表的当时待写入的Entry,如果为0，<strong>表示没有待flush的Entry,但不代表ChannelOutboundBuffer中没有Entry存在</strong>，比如调用Channel.writer方法，会往ChannelOutboundBuffer增加Entry,但在没有调用addFlush方法之前，ChannelOutboundBuffer中的flushed 字段的值不会增加。</p>
<h5 id="2-2-3-addMessage方法详解"><a href="#2-2-3-addMessage方法详解" class="headerlink" title="2.2.3  addMessage方法详解"></a>2.2.3  addMessage方法详解</h5><p><img src="https://img-blog.csdnimg.cn/20210331232959731.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>向写缓存中添加消息，方法本身的实现非常简单，因为ChannelOutboundBuffer其内部数据结构为一个链表，这是一个往链表中添加消息的过程，这里的<strong>关键点</strong>是该方法的调用入口为Channel的write方法，即调用<strong>通道的write方法只是将数据写入到写缓存，并不会触发真正的往网络中写消息。</strong></p>
<p>该方法会调用incrementPendingOutboundBytes，我们简单看一下该方法的实现细节：<br><img src="https://img-blog.csdnimg.cn/20210331233940633.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>该方法蕴含了Netty一个<strong>非常重要的机制，写操作限流，高低水位线机制。</strong><br>当缓存区中存储的数据超过了设置的高水位线(阔值)，则会设置为不可写，并向通道<strong>传播写状态变更事件</strong>。</p>
<h5 id="2-2-4-addMessage方法详解"><a href="#2-2-4-addMessage方法详解" class="headerlink" title="2.2.4  addMessage方法详解"></a>2.2.4  addMessage方法详解</h5><p><img src="https://img-blog.csdnimg.cn/20210331234441987.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><strong>该方法并没有真正的执行刷新动作，而是计算可刷写的Entry个数</strong>，一次刷新动作，会将unfluedEntry开始，一直扫描到tailEntry。</p>
<p><strong>同样这里和Netty的写限流有关，将数据刷写后，会减少缓存区中的大小，如果低于设置的低水位线，会将缓存区恢复到可写状态。</strong></p>
<p>该方法的调用入口为下图：<br><img src="https://img-blog.csdnimg.cn/20210331235204870.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br><strong>即在调用通道的flush方法时会先计算本次看刷写到Socket缓存区中的数据，然后执行flush0方法执行真正的网络写</strong>，该方法在第一部分中已详细介绍。</p>
<p>本文就介绍到这里了，文章开头的问题大家是否有了自己的理解了呢？欢迎私信或留言与我交流互动，让我们在交流中共同进步。</p>
<p><strong>一键三连是对我最大的支持与肯定</strong></p>
<p><strong>为了让大家在实战中学习Netty，笔者将RocketMQ的网络模块单独抽取成一个框架，可直接用于项目开发中，代码已上传github</strong></p>
<p><a target="_blank" rel="noopener" href="https://github.com/dingwpmz/netty-learning">Netty项目实战代码仓库</a><br><img src="https://img-blog.csdnimg.cn/20210401000506855.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>推荐阅读：</p>
<p><a target="_blank" rel="noopener" href="https://mp.weixin.qq.com/mp/homepage?__biz=MzIzNzgyMjYxOQ==&hid=14&sn=e4672a421e72968b29ad4d796a8d62b1">让天下没有难学的Netty4专栏</a></p>
<hr>
<p>见字如面,我是威哥,一个从普通二本院校毕业,从未曾接触分布式、微服务、高并发到通过<strong>技术分享</strong>实现职场蜕变，成长为RocketMQ社区优秀布道师、大厂资深架构师，出版《RocketMQ技术内幕》一书，在CSDN中记录了我的成长历程，欢迎大家关注，私信，一起交流进步。</p>
<p>分享笔者一个硬核的RocketMQ电子书：<br><img src="https://img-blog.csdnimg.cn/2021032212002741.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<p>获取方式：扫描如下二维码，关注【中间件兴趣圈】，回复RMQPDF即可获取。</p>
<p><img src="https://img-blog.csdnimg.cn/20190314214003962.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
</div>

			<script src="https://my.openwrite.cn/js/readmore.js" type="text/javascript"></script>
			<script>
			var isMobile = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i);
			if (!isMobile) {
			    var btw = new BTWPlugin();
			    btw.init({
			        "id": "vip-container",
			        "blogId": "18019-1573088808868-542",
			        "name": "中间件兴趣圈",
			        "qrcode": "https://img-blog.csdnimg.cn/20190314214003962.jpg",
			        "keyword": "more"
			    });
			}
			</script>
		
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://www.codingw.net/posts/78b6e9b7.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="中间件兴趣圈">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/posts/78b6e9b7.html" itemprop="url">读写锁在高性能消息中间件RocketMQ中的妙用</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-03-31T22:17:01+08:00">
                2021-03-31
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/rocketmq/" itemprop="url" rel="index">
                    <span itemprop="name">rocketmq</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          
             <span id="/posts/78b6e9b7.html" class="leancloud_visitors" data-flag-title="读写锁在高性能消息中间件RocketMQ中的妙用">
               <span class="post-meta-divider">|</span>
               <span class="post-meta-item-icon">
                 <i class="fa fa-eye"></i>
               </span>
               
                 <span class="post-meta-item-text">阅读次数&#58;</span>
               
                 <span class="leancloud-visitors-count"></span>
             </span>
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <div id="vip-container"><p>从源码角度研究RocketMQ不仅可以深入研究一款高性能中间件的实现原理，更是可以大大提示编码能力，更是<strong>能加强对高并发等基础框架的实战能力。</strong></p>
<p>首先我们来看一下RocketMQ NameServer的路由注册机制，从中窥探<strong>读写锁的使用场景</strong>。</p>
<h2 id="1、RocketMQ-Nameserver-路由注册机制"><a href="#1、RocketMQ-Nameserver-路由注册机制" class="headerlink" title="1、RocketMQ Nameserver 路由注册机制"></a>1、RocketMQ Nameserver 路由注册机制</h2><p><img src="https://img-blog.csdnimg.cn/20210331213103594.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<ol>
<li>Broker 每 30s 向 NameServer 发送心跳包，心跳包中包含主题的路由信息（主题的读写队列数、操作权限等），NameServer 会通过 HashMap 更新 Topic 的路由信息，并记录最后一次收到 Broker 的时间戳。</li>
</ol>
<ol start="2">
<li><p>NameServer 以每 10s 的频率清除已宕机的 Broker，NameServr 认为 Broker 宕机的依据是如果当前系统时间戳减去最后一次收到 Broker 心跳包的时间戳大于 120s。</p>
</li>
<li><p>消息生产者以每 30s 的频率去拉取主题的路由信息，即消息生产者并不会立即感知 Broker 服务器的新增与删除。</p>
</li>
</ol>
<p>在上述场景中，Broekr 会定时向 NameServer 汇报路由信息，即更新操作，消费者、生产在拉取消息、发送消息时会经常去查询 NameServer 中的路由信息，即所谓的读操作。</p>
<p>在 Broker NameServer 中使用 <strong>HashMap 来存储 topic 的路由信息</strong>。<strong>“众所周知”HashMap 在多线程环境中并不安全，多线程环境下，需要加锁。</strong></p>
<p>如下伪代码：</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> List <span class="title">getTopicInfo</span><span class="params">(String topicName)</span> </span>&#123;</span><br><span class="line">     <span class="keyword">synchronized</span>(routerInfoHashMap) &#123;</span><br><span class="line">           <span class="keyword">return</span> routerInfoHashMap.get（topicName）;</span><br><span class="line">     &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">updateTopicInfo</span><span class="params">(String topicName,List ...)</span> </span>&#123;</span><br><span class="line">     <span class="keyword">synchronized</span>(routerInfoHashMap) &#123;</span><br><span class="line">           <span class="keyword">return</span> routerInfoHashMap.put（topicName,  ...）;</span><br><span class="line">     &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>上面的代码有什么<strong>缺点</strong>呢？</p>
<p>试想一下，如果同一个时间，多个发送者，多个消费者都向nameserver查询topic的路由信息，你会发现<strong>所有的读请求都会排队执行</strong>，<strong>其并发性能可想而知的低下，此时读写锁就派上用场了。</strong></p>
<p>读写锁的使用如下：<br><img src="https://img-blog.csdnimg.cn/20210331221320804.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210331221329100.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<blockquote>
<p>上述代码来源于org.apache.rocketmq.namesrv.routeinfo.RouteInfoManager。</p>
</blockquote>
<p>这样所有的读操作能并发执行，极大的提高了性能。</p>
<p><strong>读写锁的规则：读锁与写锁互斥，读锁与读锁兼容，即可同时获取锁，举例如下：</strong></p>
<p>例如同一个时间依次有线程  t1 t2 t3 申请读锁，t4，申请写锁，t5 申请读锁：</p>
<p>首先 t1,t2,t3 成功获取锁，t4,t5阻塞等待。</p>
<p>等t1,t2,t3释放锁后，t4获取锁，t5阻塞，然后t4释放锁，t5获取锁。</p>
<p><strong>本文就介绍到这里了，读写锁的使用场景大家是否Get到了，如果本文对你有所帮助，希望一键三连表示支持。</strong></p>
<hr>
<p>见字如面,我是威哥,一个从普通二本院校毕业,从未曾接触分布式、微服务、高并发到通过<strong>技术分享</strong>实现职场蜕变，成长为RocketMQ社区优秀布道师、大厂资深架构师，出版《RocketMQ技术内幕》一书，在CSDN中记录了我的成长历程，欢迎大家关注，私信，一起交流进步。</p>
<p>分享笔者一个硬核的RocketMQ电子书：<br><img src="https://img-blog.csdnimg.cn/2021032212002741.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<p>获取方式：扫描如下二维码，关注【中间件兴趣圈】，回复RMQPDF即可获取。</p>
<p><img src="https://img-blog.csdnimg.cn/20190314214003962.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
</div>

			<script src="https://my.openwrite.cn/js/readmore.js" type="text/javascript"></script>
			<script>
			var isMobile = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i);
			if (!isMobile) {
			    var btw = new BTWPlugin();
			    btw.init({
			        "id": "vip-container",
			        "blogId": "18019-1573088808868-542",
			        "name": "中间件兴趣圈",
			        "qrcode": "https://img-blog.csdnimg.cn/20190314214003962.jpg",
			        "keyword": "more"
			    });
			}
			</script>
		
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://www.codingw.net/posts/c1cbb962.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="中间件兴趣圈">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/posts/c1cbb962.html" itemprop="url">《我要进大厂》系列之ReentrantLock与synchronized两种锁的区别</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-03-29T20:37:01+08:00">
                2021-03-29
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/%E9%9D%A2%E8%AF%95%E7%B3%BB%E5%88%97/" itemprop="url" rel="index">
                    <span itemprop="name">面试系列</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          
             <span id="/posts/c1cbb962.html" class="leancloud_visitors" data-flag-title="《我要进大厂》系列之ReentrantLock与synchronized两种锁的区别">
               <span class="post-meta-divider">|</span>
               <span class="post-meta-item-icon">
                 <i class="fa fa-eye"></i>
               </span>
               
                 <span class="post-meta-item-text">阅读次数&#58;</span>
               
                 <span class="leancloud-visitors-count"></span>
             </span>
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          
            <div id="vip-container"><p>在大厂面试中，特别是第一轮技术面试中，锁是一个非常高频的主题，在JDK中主要提供了两种方式：JUC(JAVA并发框架)中的ReentrantLock与synchronized关键字。</p>
<p>那这两种有何区别呢?在实际工作中又如何选取呢？</p>
<p>ReentrantLock 实现 synchronized 的语义，提供多线程的<strong>同步阻塞语义</strong>，即我们通常意义上的<strong>同步锁</strong>，但 ReentrantLock 的使用更加灵活，提供了synchronized关键字不具备的功能特性。</p>
<h2 id="1、可重入性"><a href="#1、可重入性" class="headerlink" title="1、可重入性"></a>1、可重入性</h2><p>ReentrantLock 与 synchronized 都是允许重入的。</p>
<p>用一段的代码说明下锁的可重入语义：<br><img src="https://img-blog.csdnimg.cn/20210327211011459.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>即一个线程在获取了一把锁后，还可以继续申请获取。</p>
<h2 id="2、ReentrantLock支持公平-非公平锁"><a href="#2、ReentrantLock支持公平-非公平锁" class="headerlink" title="2、ReentrantLock支持公平/非公平锁"></a>2、ReentrantLock支持公平/非公平锁</h2><p><img src="https://img-blog.csdnimg.cn/20210327211439929.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>NonFairSync 非公平锁：在申请锁时首先尝试去<strong>抢占锁</strong>，如果未成功获取锁后才无奈进入排队队列。</p>
<p>FairSync 公平锁：在申请锁时会进入锁排队队列，中规中矩的等待锁。</p>
<p>例如当前有3个线程在排队等待锁，持有锁的线程执行完临界区代码后释放锁时，正好又来了一个新的线程也需要获取锁，<strong>如果是公平锁，则需要等待前面3个线程获取锁后才能获取锁</strong>。如果是<strong>非公平锁</strong>，他会尝试与锁排队队列总的其他线程一起竞争锁，<strong>有可能直接获取锁就执行，这对已在队列中排队的线程就显得不那么公平了。</strong></p>
<p><strong>通常情况下，非公平锁涉及更少的线程切换开销，整体性能比公平锁要高</strong>。</p>
<h2 id="3、ReentrantLock支持锁等待超时"><a href="#3、ReentrantLock支持锁等待超时" class="headerlink" title="3、ReentrantLock支持锁等待超时"></a>3、ReentrantLock支持锁等待超时</h2><p>ReentrantLock 在申请锁时支持设置超时时间，即在多久未成功获得锁时被自动唤醒，可通过 tryLock 方法实现。<br><img src="https://img-blog.csdnimg.cn/20210327213033602.png" alt="在这里插入图片描述"><br>该方法获取锁后会立即返回，否则会阻塞，直到超时，返回true时表示成功获取锁。</p>
<h2 id="４、ReentrantLock-支持中断"><a href="#４、ReentrantLock-支持中断" class="headerlink" title="４、ReentrantLock 支持中断"></a>４、ReentrantLock 支持中断</h2><p>ReentrantLock 提供了 lockInterruptibly()  方法实现<strong>可中断的锁申请</strong>，即一个线程在竞争锁而陷入阻塞状态后，可以由<strong>另个一个线程</strong>来中断这个线程，从而放弃锁申请。</p>
<p>其实现是通过LockSupport的park()方法。<br><img src="https://img-blog.csdnimg.cn/20210327214416950.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>LockSupport 的 park 方法能响应中断，如果线程被中断，<strong>该方法能停止阻塞而被返回，但不会抛出 InterruptedException</strong> 。</p>
<h2 id="５、ReentrantLock-支持灵活的监控"><a href="#５、ReentrantLock-支持灵活的监控" class="headerlink" title="５、ReentrantLock 支持灵活的监控"></a>５、ReentrantLock 支持灵活的监控</h2><p><img src="https://img-blog.csdnimg.cn/20210327231454357.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>ReentrantLock提供了众多的方法，对开发监控锁相关的应用程序有莫大的帮助。</p>
<h2 id="６、总结"><a href="#６、总结" class="headerlink" title="６、总结"></a>６、总结</h2><p>ReentrantLock与synchronized具有相同的同步阻塞语义，从jdk1.6以后，两者的性能相差无几，如果没有特殊的使用需求，直接使用synchronized即可，因为锁的内部存储结构由虚拟机负责，使用简单。</p>
<hr>
<p>见字如面,我是威哥,一个从普通二本院校毕业,从未曾接触分布式、微服务、高并发到通过<strong>技术分享</strong>实现职场蜕变，成长为RocketMQ社区优秀布道师、大厂资深架构师，出版《RocketMQ技术内幕》一书，在CSDN中记录了我的成长历程，欢迎大家关注，私信，一起交流进步。</p>
<p>分享笔者一个硬核的RocketMQ电子书：<br><img src="https://img-blog.csdnimg.cn/2021032212002741.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
<p>获取方式：扫描如下二维码，关注【中间件兴趣圈】，回复RMQPDF即可获取。</p>
<p><img src="https://img-blog.csdnimg.cn/20190314214003962.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3ByZXN0aWdlZGluZw==,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p>
</div>

			<script src="https://my.openwrite.cn/js/readmore.js" type="text/javascript"></script>
			<script>
			var isMobile = navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i);
			if (!isMobile) {
			    var btw = new BTWPlugin();
			    btw.init({
			        "id": "vip-container",
			        "blogId": "18019-1573088808868-542",
			        "name": "中间件兴趣圈",
			        "qrcode": "https://img-blog.csdnimg.cn/20190314214003962.jpg",
			        "keyword": "more"
			    });
			}
			</script>
		
          
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
      

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://www.codingw.net/posts/194b9de.html">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="">
      <meta itemprop="description" content="">
      <meta itemprop="image" content="/images/avatar.gif">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="中间件兴趣圈">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">
                
                <a class="post-title-link" href="/posts/194b9de.html" itemprop="url">这些关于线程池相关的高频面试题你都知道吗</a></h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2021-03-24T23:52:54+08:00">
                2021-03-24
              </time>
            

            

            
          </span>

          
            <span class="post-category" >
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/java/" itemprop="url" rel="index">
                    <span itemprop="name">java</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          
             <span id="/posts/194b9de.html" class="leancloud_visitors" data-flag-title="这些关于线程池相关的高频面试题你都知道吗">
               <span class="post-meta-divider">|</span>
               <span class="post-meta-item-icon">
                 <i class="fa fa-eye"></i>
               </span>
               
                 <span class="post-meta-item-text">阅读次数&#58;</span>
               
                 <span class="leancloud-visitors-count"></span>
             </span>
          

          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        
          <h4 id="1、java-线程池的核心属性有哪些，其含义是什么？"><a href="#1、java-线程池的核心属性有哪些，其含义是什么？" class="headerlink" title="1、java 线程池的核心属性有哪些，其含义是什么？"></a>1、java 线程池的核心属性有哪些，其含义是什么？</h4><p>JAVA 线程池的核心属性如下：</p>
<ul>
<li>int corePoolSize<br>核心线程数</li>
<li>int maximumPoolSize<br>线程池最大线程数</li>
<li>long keepAliveTime<br>线程保持活跃的时间</li>
<li>TimeUnit unit<br>keepAliveTime 的时间单位</li>
<li>BlockingQueue&lt; Runnable &gt; workQueue<br>任务挤压队列</li>
<li>ThreadFactory threadFactory<br>线程创建工厂类</li>
<li>RejectedExecutionHandler handler<br>拒绝策略</li>
</ul>
<h4 id="2、向线程池提交任务时线程创建过程？"><a href="#2、向线程池提交任务时线程创建过程？" class="headerlink" title="2、向线程池提交任务时线程创建过程？"></a>2、向线程池提交任务时线程创建过程？</h4><p>那当用户向线程池提交一个任务的时候，线程池会如何创建线程呢？</p>
          <!--noindex-->
          <div class="post-button text-center">
            <a class="btn" href="/posts/194b9de.html#more" rel="contents">
              阅读全文 &raquo;
            </a>
          </div>
          <!--/noindex-->
        
      
    </div>
    
    
    

    

    

    

    <footer class="post-footer">
      

      

      

      
      
        <div class="post-eof"></div>
      
    </footer>
  </div>
  
  
  
  </article>


    
  </section>

  
  <nav class="pagination">
    <span class="page-number current">1</span><a class="page-number" href="/page/2/">2</a><span class="space">&hellip;</span><a class="page-number" href="/page/14/">14</a><a class="extend next" rel="next" href="/page/2/">&gt;</a>
  </nav>



          </div>
          


          

        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      

      <section class="site-overview-wrap sidebar-panel sidebar-panel-active">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <p class="site-author-name" itemprop="name"></p>
              <p class="site-description motion-element" itemprop="description"></p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/">
              
                  <span class="site-state-item-count">139</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">18</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            

          </nav>

          

          

          
          

          
          

          

        </div>
      </section>

      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2021</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">中间件兴趣圈</span>

  
</div>


  <div class="powered-by">由 <a class="theme-link" target="_blank" href="https://hexo.io">Hexo</a> 强力驱动</div>



  <span class="post-meta-divider">|</span>



  <div class="theme-info">主题 &mdash; <a class="theme-link" target="_blank" href="https://github.com/iissnan/hexo-theme-next">NexT.Muse</a> v5.1.4</div>




        
<div class="busuanzi-count">
  <script async src="https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script>

  
    <span class="site-uv">
      <i class="fa fa-user"></i>
      <span class="busuanzi-value" id="busuanzi_value_site_uv"></span>
      
    </span>
  

  
    <span class="site-pv">
      <i class="fa fa-eye"></i>
      <span class="busuanzi-value" id="busuanzi_value_site_pv"></span>
      
    </span>
  
</div>








        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  

  

  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  





  

  
  <script src="https://cdn1.lncld.net/static/js/av-core-mini-0.6.4.js"></script>
  <script>AV.initialize("NNEhOL0iOcflg8f1U3HUqiCq-gzGzoHsz", "7kSmkbbb3DktmHALlShDsBUF");</script>
  <script>
    function showTime(Counter) {
      var query = new AV.Query(Counter);
      var entries = [];
      var $visitors = $(".leancloud_visitors");

      $visitors.each(function () {
        entries.push( $(this).attr("id").trim() );
      });

      query.containedIn('url', entries);
      query.find()
        .done(function (results) {
          var COUNT_CONTAINER_REF = '.leancloud-visitors-count';

          if (results.length === 0) {
            $visitors.find(COUNT_CONTAINER_REF).text(0);
            return;
          }

          for (var i = 0; i < results.length; i++) {
            var item = results[i];
            var url = item.get('url');
            var time = item.get('time');
            var element = document.getElementById(url);

            $(element).find(COUNT_CONTAINER_REF).text(time);
          }
          for(var i = 0; i < entries.length; i++) {
            var url = entries[i];
            var element = document.getElementById(url);
            var countSpan = $(element).find(COUNT_CONTAINER_REF);
            if( countSpan.text() == '') {
              countSpan.text(0);
            }
          }
        })
        .fail(function (object, error) {
          console.log("Error: " + error.code + " " + error.message);
        });
    }

    function addCount(Counter) {
      var $visitors = $(".leancloud_visitors");
      var url = $visitors.attr('id').trim();
      var title = $visitors.attr('data-flag-title').trim();
      var query = new AV.Query(Counter);

      query.equalTo("url", url);
      query.find({
        success: function(results) {
          if (results.length > 0) {
            var counter = results[0];
            counter.fetchWhenSave(true);
            counter.increment("time");
            counter.save(null, {
              success: function(counter) {
                var $element = $(document.getElementById(url));
                $element.find('.leancloud-visitors-count').text(counter.get('time'));
              },
              error: function(counter, error) {
                console.log('Failed to save Visitor num, with error message: ' + error.message);
              }
            });
          } else {
            var newcounter = new Counter();
            /* Set ACL */
            var acl = new AV.ACL();
            acl.setPublicReadAccess(true);
            acl.setPublicWriteAccess(true);
            newcounter.setACL(acl);
            /* End Set ACL */
            newcounter.set("title", title);
            newcounter.set("url", url);
            newcounter.set("time", 1);
            newcounter.save(null, {
              success: function(newcounter) {
                var $element = $(document.getElementById(url));
                $element.find('.leancloud-visitors-count').text(newcounter.get('time'));
              },
              error: function(newcounter, error) {
                console.log('Failed to create');
              }
            });
          }
        },
        error: function(error) {
          console.log('Error:' + error.code + " " + error.message);
        }
      });
    }

    $(function() {
      var Counter = AV.Object.extend("Counter");
      if ($('.leancloud_visitors').length == 1) {
        addCount(Counter);
      } else if ($('.post-title-link').length > 1) {
        showTime(Counter);
      }
    });
  </script>



  

  

  
  

  

  

  

</body>
</html>
